/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <unordered_map>
#include "dal/allocator.h"
#include "dal_cyclonedds/utils/ReferenceCount.hpp"
#include "dal_cyclonedds/converter/StatusConverter.hpp"
#include "dal_cyclonedds/converter/StatusMaskConverter.hpp"
#include "dal_cyclonedds/converter/QosConverter.hpp"
#include "dal_cyclonedds/converter/RetTypeConverter.hpp"
#include "dal_cyclonedds/listener_impl/TopicListenerImpl.hpp"
#include "dal_cyclonedds/utils/log.hpp"
#include "dal_cyclonedds/utils/TopicSertypeManager.hpp"
#include "dal_cyclonedds/utils/EntityGetter.hpp"
#include "dal_cyclonedds/utils/TopicSertypeManager.hpp"

#include "dal_utils/StrHelper.hpp"
#include "dal_utils/MsgLibLoader.hpp"

#include "dal_msg/dalidl_runtime_c/message_type_support_struct.h"
#include "dalidl_typesupport_introspection_cpp/identifier.hpp"
#include "dalidl_typesupport_introspection_cpp/message_introspection.hpp"
#include "MessageTypeSupport.hpp"
#include "serdata.hpp"
#include "TypeSupport2.hpp"

using MessageTypeSupport_c =
    dal_cyclonedds_cpp::MessageTypeSupport<dalidl_typesupport_introspection_c__MessageMembers>;
using MessageTypeSupport_cpp =
    dal_cyclonedds_cpp::MessageTypeSupport<dalidl_typesupport_introspection_cpp::MessageMembers>;

static bool is_type_self_contained(const dalidl_message_type_support_t* type_supports)
{
    auto ts = get_message_typesupport_handle(
        type_supports, dalidl_typesupport_introspection_cpp::typesupport_identifier);
    if (ts != nullptr) {  // CPP typesupport
        auto members =
            static_cast<const dalidl_typesupport_introspection_cpp::MessageMembers*>(ts->data);
        MessageTypeSupport_cpp mts(members);
        return mts.is_type_self_contained();
    } else {
        ts = get_message_typesupport_handle(type_supports,
                                            dalidl_typesupport_introspection_c__identifier);
        if (ts != nullptr) {  // C typesupport
            auto members =
                static_cast<const dalidl_typesupport_introspection_c__MessageMembers*>(ts->data);
            MessageTypeSupport_c mts(members);
            return mts.is_type_self_contained();
        } else {
            DAL_SET_ERROR_MSG("Non supported type-supported");
            return false;
        }
    }
}

dal_type_support_t* dal_create_type_support(void* type_support)
{
    if (!type_support) {
        LOG_ERROR() << "TypeSupport is null. " << LOG_ENDL;
        return nullptr;
    }

    // fully qualified name
    const char* fqname = static_cast<const char*>(type_support);
    if (!MsgLibLoader::loadLibrary(fqname)) {
        LOG_ERROR() << "Load msg lib failed." << LOG_ENDL;
        return nullptr;
    }

    void* func = MsgLibLoader::getFunction(fqname);
    if (!func) {
        LOG_ERROR() << "Get function failed." << LOG_ENDL;
        return nullptr;
    }

    const dalidl_message_type_support_t* ts_handles =
        reinterpret_cast<const dalidl_message_type_support_t* (*)()>(func)();
    if (!ts_handles) {
        LOG_ERROR() << "Get message type supports failed." << LOG_ENDL;
        return nullptr;
    }

    const dalidl_message_type_support_t* ts_handle = get_message_typesupport_handle(
        ts_handles, dalidl_typesupport_introspection_cpp::typesupport_identifier);
    if (!ts_handle) {
        LOG_ERROR() << "Get message type support handle failed." << LOG_ENDL;
        return nullptr;
    }

    bool is_fixed_type = is_type_self_contained(ts_handle);
    uint32_t sample_size = static_cast<uint32_t>(dal_cyclonedds_cpp::get_message_size(ts_handle));
    auto sertype = create_sertype(
        ts_handle->typesupport_identifier,
        create_message_type_support(ts_handle->data, ts_handle->typesupport_identifier),
        false,
        dal_cyclonedds_cpp::make_message_value_type(ts_handles),
        sample_size,
        is_fixed_type);

    auto ts = dal_type_support_allocate();
    ts->handle_ = sertype;
    ts->type_name_ = dal_strdup(fqname);

    return ts;
}

dal_ret_t dal_delete_type_support(dal_type_support_t* ts)
{
    if (ts == nullptr || ts->handle_ == nullptr)
        return DAL_RETCODE_BAD_PARAMETER;

    TopicSertypeManager::GetInstance()->RemoveTopicSerTypeByTypeName(ts->type_name_);
    dal_type_support_free(ts);
    reinterpret_cast<dal_type_support_t*>(ts)->handle_ = nullptr;
    return DAL_RETCODE_OK;
}

const char* dal_type_support_get_type_name(dal_type_support_t* ts)
{
    if (ts == nullptr) {
        LOG_ERROR() << "TypeSupport is null." << LOG_ENDL;
        return nullptr;
    }
    return ts->type_name_;
}

dal_ret_t dal_type_support_register_type(dal_type_support_t* ts,
                                         dal_domain_participant_t* participant,
                                         const char* type_name)
{
    if (ts == nullptr || participant == nullptr)
        return DAL_RETCODE_BAD_PARAMETER;

    if (type_name == nullptr || strlen(type_name) == 0)
        return DAL_RETCODE_BAD_PARAMETER;

    std::string type_name_str = std::string(type_name);
    struct sertype_dal* sertype = reinterpret_cast<struct sertype_dal*>(ts->handle_);
    dds_entity_t c_participant = (reinterpret_cast<DalDomainParticipantImpl*>(participant->handle_))
                                     ->CycloneddsDomainParticipant();
    TopicSertypeManager::GetInstance()->AddTopicSerType(c_participant, type_name_str, sertype);
    return DAL_RETCODE_OK;
}

dal_topic_listener_t* dal_create_topic_listener(dal_topic_listener_callbacks_t callbacks)
{
    auto listener = dal_topic_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = new TopicListenerImpl(callbacks);
    if (!listener->handle_)
        LOG_ERROR() << "TopicListenerImpl memory allocation failed." << LOG_ENDL;
    return listener;
}

dal_ret_t dal_delete_topic_listener(dal_topic_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    auto c_listener_impl = reinterpret_cast<TopicListenerImpl*>(listener->handle_);
    if (c_listener_impl->ref_count_.Get() > 0) {
        LOG_ERROR() << "can't delete topic listener because it's entity still exist." << LOG_ENDL;
        return DAL_RETCODE_PRECONDITION_NOT_MET;
    }

    delete c_listener_impl;
    dal_topic_listener_free(listener);
    reinterpret_cast<dal_topic_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_topic_t* dal_domain_participant_create_topic(dal_domain_participant_t* participant,
                                                 const char* topic_name,
                                                 const char* type_name,
                                                 dal_topic_qos_t* qos,
                                                 dal_topic_listener_t* listener,
                                                 dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (!topic_name || !type_name || strlen(topic_name) == 0 || strlen(type_name) == 0) {
        LOG_ERROR_WITH_LOCATION() << "topic or type name is invalid parameter." << LOG_ENDL;
        return nullptr;
    }

    dds_entity_t c_participant = get_cyclone_domain_participant(participant);
    if (c_participant < 0) {
        LOG_ERROR_WITH_LOCATION() << "cyclone participant is invalid." << LOG_ENDL;
        return nullptr;
    }

    std::string type_name_str = std::string(type_name);
    auto sertype =
        TopicSertypeManager::GetInstance()->GetTopicSerType(c_participant, type_name_str);
    if (!sertype) {
        LOG_ERROR_WITH_LOCATION() << "not registered type_name." << LOG_ENDL;
        return nullptr;
    }

    std::string topic_name_str = std::string(topic_name);
    if (TopicSertypeManager::GetInstance()->IsTopicNameExist(c_participant, topic_name_str)) {
        LOG_ERROR_WITH_LOCATION() << "topic name already exist." << LOG_ENDL;
        return nullptr;
    }

    auto d_topic = dal_topic_allocate();
    if (!d_topic) {
        LOG_ERROR_WITH_LOCATION() << "dal topic allocate failed." << LOG_ENDL;
        return nullptr;
    }

    auto c_listener_impl =
        listener ? reinterpret_cast<TopicListenerImpl*>(listener->handle_) : nullptr;
    dds_listener_t* c_listener = NULL;
    if (c_listener_impl != nullptr) {
        c_listener = c_listener_impl->CycloneDDSListener();
    }

    dds_qos_t* c_qos = dds_create_qos();
    d_ret = QosConverter::GetInstance()->ConvertTopicQosFromDalToCyclonedds(qos, c_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert topic qos failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        dds_delete_qos(c_qos);
        return nullptr;
    }

    ddsi_sertype* p = (ddsi_sertype*)sertype;
    dds_entity_t c_topic =
        dds_create_topic_sertype(c_participant, topic_name, &p, c_qos, c_listener, nullptr);

    // One sertype may be used for muiltiple topic, when topic is deleted, sertype will be unref.
    // So we have to increase the reference count of sertype when creating topic.
    p = ddsi_sertype_ref(p);

    // Update new sertype pointer because it may changed.
    TopicSertypeManager::GetInstance()->AddTopicSerType(
        c_participant, type_name_str, (sertype_dal*)p);

    dds_delete_qos(c_qos);
    if (c_topic < 0) {
        LOG_ERROR_WITH_LOCATION() << "create topic failed." << LOG_ENDL;
        dal_topic_free(d_topic);
        return nullptr;
    }

    d_topic->handle_ = new DalTopicImpl(c_topic, c_listener_impl);

    uint32_t c_mask = 0;
    StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToCyclonedds(mask, &c_mask);
    StatusMaskConverter::GetInstance()->FilterTopicStatusMask(&c_mask);
    if (dds_set_status_mask(c_topic, c_mask) != DDS_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "set topic status mask failed." << LOG_ENDL;
        return d_topic;
    }

    TopicSertypeManager::GetInstance()->AddTopicName(c_participant, topic_name_str);
    TopicSertypeManager::GetInstance()->AddTopicInfo(c_topic, topic_name, type_name);

    TopicSertypeManager::GetInstance()->AddEntityToParticipant(c_participant, c_topic);

    return d_topic;
}

dal_topic_t* dal_domain_participant_create_topic_with_profile(dal_domain_participant_t* participant,
                                                              const char* topic_name,
                                                              const char* type_name,
                                                              dal_topic_qos_t* qos,
                                                              const char* profile_name,
                                                              dal_topic_listener_t* listener,
                                                              dal_status_mask_t mask)
{
    return dal_domain_participant_create_topic(
        participant, topic_name, type_name, qos, listener, mask);
}

dal_ret_t dal_domain_participant_delete_topic(dal_domain_participant_t* participant,
                                              dal_topic_t* topic)
{
    dds_return_t c_ret;
    dds_entity_t c_participant = get_cyclone_domain_participant(participant);
    if (c_participant <= 0) {
        LOG_ERROR_WITH_LOCATION() << "cyclone participant is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    dds_entity_t c_topic = get_cyclone_topic(topic);
    if (c_topic <= 0) {
        LOG_ERROR_WITH_LOCATION() << "cyclone topic is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    if (c_participant != dds_get_parent(c_topic)) {
        LOG_ERROR_WITH_LOCATION() << "topic is not belong to participant." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    std::string topic_name = TopicSertypeManager::GetInstance()->GetTopicName(c_topic);
    std::string type_name = TopicSertypeManager::GetInstance()->GetTypeName(c_topic);

    // sertype_dal* sertype1 = TopicSertypeManager::GetInstance()->GetTopicSerType(c_participant,
    // type_name);

    c_ret = dds_delete(c_topic);
    dal_ret_t d_ret = RetTypeConverter::GetInstance()->ConvertRetFromCycloneddsToDal(c_ret);
    if (d_ret != DAL_RETCODE_OK) {
        LOG_ERROR_WITH_LOCATION() << "delete topic failed." << LOG_ENDL;
        return d_ret;
    }

    // after dds_delete(topic), sertype will be modified, and sertype cannot be used to create topic
    // again. so we don't delete cyclone's topic but only delete dal's topic, and we add this
    // cyclone's topic to undeleted_topic_map.
    // TopicSertypeManager::GetInstance()->AddUndeletedTopic(c_participant, c_topic);

    // sertype_dal* sertype2 = TopicSertypeManager::GetInstance()->GetTopicSerType(c_participant,
    // type_name);

    auto topic_impl = reinterpret_cast<DalTopicImpl*>(topic->handle_);
    delete topic_impl;
    dal_topic_free(topic);
    reinterpret_cast<dal_topic_t*>(topic)->handle_ = nullptr;

    TopicSertypeManager::GetInstance()->RemoveTopicName(c_participant, topic_name);
    TopicSertypeManager::GetInstance()->DeleteTopicInfo(c_topic);

    TopicSertypeManager::GetInstance()->RemoveEntityFromParticipant(c_participant, c_topic);

    return DAL_RETCODE_OK;
}